package com.airport.schedule.greedy;

import com.airport.schedule.Problem;
import com.airport.schedule.ScheduleSolution;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.MapUtil;
import com.airport.schedule.util.SetUtil;

import java.util.*;

public class GreedyDayByDayScheduler {

    private Problem problem;
    private ScheduleSolution solution;

    private Map<Flight, Set<FlightGate>> flightAvailableGates;
    private Map<Flight, Double> flight2ConflictRate;
    private List<Flight> unassignedFlights;
    private Random random = new Random(System.currentTimeMillis());

    private double gamma = 0.98;
    private double alpha = 0.01;

    private boolean considerAdHoc;

    public GreedyDayByDayScheduler(ScheduleSolution solution, Problem problem, boolean considerAdHoc) {
        this.problem = problem;
        this.solution = solution;
        this.considerAdHoc = considerAdHoc;
        this.unassignedFlights = new ArrayList<>();
        this.flight2ConflictRate = new HashMap<>();
        this.flightAvailableGates = new HashMap<>();

        List<Flight> flights = problem.getScheduleFlights();
        flights.forEach(iFlight->{
            Set<FlightGate> iGates = solution.getCurrentAvailableGates(iFlight, considerAdHoc);
            flightAvailableGates.put(iFlight, iGates);
            flight2ConflictRate.put(iFlight, 0.0);
            flights.forEach(jFlight->{
                Set<FlightGate> jGates = solution.getCurrentAvailableGates(jFlight, considerAdHoc);
                Set<FlightGate> common = SetUtil.intersect(iGates, jGates);
                flight2ConflictRate.put(iFlight, flight2ConflictRate.get(iFlight)
                        + (common.size()+0.0)/jGates.size());
            });
        });
        System.out.println("GreedyDayByDayScheduler init successfully!");
    }

    public void process() {
        List<Flight> flights = new ArrayList<>(problem.getScheduleFlights());
        Collections.sort(flights, (f1, f2)-> {
            int r = Double.compare(flight2ConflictRate.get(f1), flight2ConflictRate.get(f2));
            if(r == 0) {
                int peopleNum1 = f1.getNumPeople();
                int peopleNum2 = f2.getNumPeople();
                if(peopleNum1 == peopleNum2) {
                    int i1 = flightAvailableGates.get(f1).size();
                    int i2 = flightAvailableGates.get(f2).size();
                    return i2 - i1;
                }
                return peopleNum2 - peopleNum1;
            }
            return r;
        });
        while(!flights.isEmpty()) {
            Flight flight = flights.remove(0);
            List<FlightGate> unoccupiedNormalGates = solution.getUnoccupiedNormalFlightGates(flight, considerAdHoc);
            if(!unoccupiedNormalGates.isEmpty()) {// 贪心策略二：先优先正常机位
                if(unoccupiedNormalGates.size() == 1) { // 只有一个正常机位 看看要不要霸占
                    solution.assign(flight, unoccupiedNormalGates.get(0));
                }else{
                    FlightGate gate = findBestFlightGate(unoccupiedNormalGates, solution, flight, flights);
                    solution.assign(flight, gate);
                }
            }else{ // 没有正常机位，除非采取临时措施！
                unassignedFlights.add(flight);
            }
        }
    }

    private FlightGate findBestFlightGate(List<FlightGate> candidates, ScheduleSolution solution,
                                          Flight curFlight, List<Flight> unassignedFlights) {
        Map<FlightGate, Double> gateOccupiedRate = new HashMap<>();
        Map<FlightGate, Double> gateRunwayConflictRate = new HashMap<>();
        for(FlightGate g : candidates) {
            gateOccupiedRate.put(g, 0.0);
            gateRunwayConflictRate.put(g, 0.0);
        }
        Set<FlightGate> curFlightGates = new HashSet<>(candidates);
        for(Flight comingFlight : unassignedFlights) {
            Set<FlightGate> comingFlightGates =
                    new HashSet<>(solution.getUnoccupiedNormalFlightGates(comingFlight, true));
            if(SetUtil.hasIntersect(curFlightGates, comingFlightGates)) {
                for(FlightGate gate : curFlightGates) {
                    if(comingFlightGates.contains(gate)) {
                        gateOccupiedRate.put(gate, gateOccupiedRate.get(gate)+1.0/comingFlightGates.size());
                    }
                }
            }
        }
        for(FlightGate gate : candidates) {
            solution.assign(curFlight, gate);
            int conflictNum = solution.getRunwayConflictNum();
            solution.remove(curFlight, gate);
            gateRunwayConflictRate.put(gate, gateRunwayConflictRate.get(gate)+conflictNum);
        }
        MapUtil.normalize(gateOccupiedRate);
        MapUtil.normalize(gateRunwayConflictRate);
        double minVal = Double.MAX_VALUE;
        List<FlightGate> bestGates = new ArrayList<>();
        for(Map.Entry<FlightGate, Double> entry : gateOccupiedRate.entrySet()) {
            double value = (1 - gamma) * entry.getValue() + gamma * gateRunwayConflictRate.get(entry.getKey());
            if(entry.getKey().isNearGate()) {
                value -= alpha;
            }
            if(value == minVal) {
                bestGates.add(entry.getKey());
            }else if(value < minVal){
                bestGates = new ArrayList<>();
                bestGates.add(entry.getKey());
                minVal = value;
            }
        }
        return bestGates.get(random.nextInt(bestGates.size()));
    }

}
